//
//  SessionStore.swift
//  Link
//
//  Created by melvyn on 2018/9/20.
//  Copyright © 2018 NEET. All rights reserved.
//

import Foundation
import Alamofire

open class SessionStore<E: SessionType> {
    
    private enum Destination {
        case memory
        case disk
        case both
        
        static var path: URL {
            var path = URL.init(fileURLWithPath: NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first!)
            path.appendPathComponent("akii.me.session.store", isDirectory: true)
            return path
        }
        
        static func url(_ id: E.ID) -> (relativeURL: URL, url: URL) {
            
            guard FileManager.default.fileExists(atPath: path.path) else {
                try! FileManager.default.createDirectory(at: path, withIntermediateDirectories: true, attributes: nil)
                return (path, URL.init(fileURLWithPath: path.path + "\(id)"))
            }
            return (path, URL.init(fileURLWithPath: path.path + "\(id)"))
        }
    }
    
    private let queue = DispatchQueue.init(label: "akii.me.store.session.queue." + UUID().uuidString,
                                           qos: DispatchQoS.utility,
                                           attributes: .concurrent)
    private var caches: [E] = []
    
    /// current active session.
    public var session: E? {
        return all().last
    }
    
    var userDidSaved: ((E) -> Void)? = nil
    var userDidLogout: ((E.ID) -> Void)? = nil
    
    init() {}
    
    private func all() -> [E] {
        return queue.sync {
            return caches
        }
    }
    
    func save(_ session: E, completionHandler: @escaping (Result<E>) -> Void) {
        queue.async(flags: .barrier) { [weak self] in
            self?.storage(session, destination: .both, completionHandler: completionHandler)
        }
    }
    
    private func storage(_ session: E, destination: Destination, completionHandler: @escaping (Result<E>) -> Void) {
        func cache(_ session: E) {
            caches.removeAll { $0 == session }
            caches.append(session)
        }
        
        switch destination {
        case .memory:
            cache(session)
            completionHandler(.success(session))
            userDidSaved?(session)
        case .disk:
            do {
                let metadata = try JSONAdapter.encode(session)
                try metadata.write(to: Destination.url(session.id).url, options: [])
                userDidSaved?(session)
                completionHandler(.success(session))
            } catch let error { completionHandler(.failure(error)) }
        case .both:
            do {
                cache(session)
                let metadata = try JSONAdapter.encode(session)
                try metadata.write(to: Destination.url(session.id).url, options: [])
                userDidSaved?(session)
                completionHandler(.success(session))
            } catch let error { completionHandler(.failure(error)) }
        }
    }
    
    
    func remove(_ identifier: E.ID) {
        queue.async(flags: .barrier) { [weak self] in
            guard let strongSelf = self else { return }
            
            strongSelf.caches.removeAll { $0.id == identifier }
            
            let path = Destination.url(identifier)
            if FileManager.default.fileExists(atPath: path.relativeURL.path) {
                try! FileManager.default.removeItem(atPath: path.url.path)
            }
            self?.userDidLogout?(identifier)
        }
    }
    
    func removeAll() {
        queue.async(flags: .barrier) { [weak self] in
            guard let strongSelf = self else { return }
            strongSelf.caches.removeAll()
            
            let subpaths = FileManager.default.subpaths(atPath: Destination.path.path)
            
            do {
                try subpaths?.forEach({
                    try FileManager.default.removeItem(atPath: $0)
                })
            } catch let error {
                #if DEBUG
                print(error.localizedDescription)
                #endif
            }
        }
    }
}

extension SessionStore where E: AuthSessionType {
    
    public func session(with uid: E.ID) -> E? {
        return all().first(where: { $0.id == uid })
    }
    
    public func hasLoggedInUser() -> Bool {
        return all().count > 0
    }
    
    public func exsitingUserSessions() -> [E] {
        return all()
    }
    
    func logout(with uid: E.ID) {
        remove(uid)
    }
}
